package com.deluxelx.soul.inn.service.article.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.deluxelx.soul.inn.bean.article.Article;
import com.deluxelx.soul.inn.bean.article.ArticleDTO;
import com.deluxelx.soul.inn.bean.article.ArticleDetailVO;
import com.deluxelx.soul.inn.bean.article.ArticleVO;
import com.deluxelx.soul.inn.bean.counselor.Counselor;
import com.deluxelx.soul.inn.common.enums.NavbarEnum;
import com.deluxelx.soul.inn.common.param.CollectedParam;
import com.deluxelx.soul.inn.common.util.DateTimeUtil;
import com.deluxelx.soul.inn.common.util.RedisUtil;
import com.deluxelx.soul.inn.common.util.supportIds.SnowflakeId;
import com.deluxelx.soul.inn.mapper.article.ArticleMapper;
import com.deluxelx.soul.inn.mapper.common.IndexDataSupport;
import com.deluxelx.soul.inn.mapper.counselor.CounselorMapper;
import com.deluxelx.soul.inn.service.article.ArticleService;
import com.deluxelx.soul.inn.service.common.FollowRelationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static com.deluxelx.soul.inn.common.constant.ArticleConstant.*;
import static com.deluxelx.soul.inn.common.constant.IndexType.ARTICLE;

/**
 * 文章服务实现类
 *
 * @author: zihao.liao
 * @date: 2024/2/6 19:11
 */
@Service
@Slf4j
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    @Resource
    private ArticleMapper articleMapper;

    @Resource
    private CounselorMapper counselorMapper;

    @Resource
    private IndexDataSupport indexDataSupport;

    @Resource
    private FollowRelationService followRelationService;

    @Resource
    private SnowflakeId snowflake;

    @Resource
    private RedisUtil redisUtil;

    @Override
    public List<ArticleVO> fetchIndexArticleList() {
        List<String> indexArticleIds = indexDataSupport.selectIndexDataByType(ARTICLE);
        if (CollectionUtil.isEmpty(indexArticleIds)) {
            return Collections.emptyList();
        }
        List<ArticleVO> articleVOS = articleMapper.selectArticleListByIds(indexArticleIds);
        if (CollectionUtil.isEmpty(articleVOS)) {
            return Collections.emptyList();
        }
        buildStandardArticleVO(articleVOS);
        return articleVOS;
    }

    @Override
    public List<ArticleVO> fetchMyFollowArticle(String userId) {
        List<String> articleIds = followRelationService.getMyFollowIds(userId, ARTICLE_FOLLOW_KEY);
        if (CollectionUtil.isEmpty(articleIds)) {
            return Collections.emptyList();
        }
        List<ArticleVO> articleVOS = articleMapper.selectArticleListByIds(articleIds);
        buildStandardArticleVO(articleVOS);
        return articleVOS;
    }

    @Override
    public List<ArticleVO> fetchSearchArticleList(String keyword) {
        List<ArticleVO> articleVOS = articleMapper.selectListByFuzzySearch(keyword);
        if (CollectionUtil.isEmpty(articleVOS)) {
            return Collections.emptyList();
        }
        buildStandardArticleVO(articleVOS);
        return articleVOS;
    }

    @Override
    public List<ArticleVO> fetchArticleListByCondition(String typeId, Integer limit) {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        List<Article> articles;
        // TODO 这部分还有点 bug，和前端联调时才看看怎么改吧，感觉得大改
        if (StrUtil.isNotBlank(typeId)) {
            queryWrapper.eq(Article::getType, typeId)
                    .select(Article::getId);
            articles = articleMapper.selectList(queryWrapper);
        } else {
            articles = articleMapper.selectPage(new Page<>((limit / 10) + 1, 10), null)
                    .getRecords();
        }
        if (CollectionUtil.isEmpty(articles)) {
            return Collections.emptyList();
        }
        List<ArticleVO> articleVOS = articleMapper.selectArticleListByIds(articles.stream()
                .map(Article::getId)
                .collect(Collectors.toList()));
        if (CollectionUtil.isEmpty(articleVOS)) {
            return Collections.emptyList();
        }
        buildStandardArticleVO(articleVOS);
        return articleVOS;
    }

    @Override
    public List<ArticleVO> fetchArticleListByCounselor(String counselorId) {
        List<ArticleVO> articleVOList = articleMapper.selectArticleListByCounselorId(counselorId);
        if (CollectionUtil.isEmpty(articleVOList)) {
            return Collections.emptyList();
        }
        buildStandardArticleVO(articleVOList);
        return articleVOList;
    }

    @Override
    public ArticleDetailVO fetchArticleDetail(String articleId) {
        Article article = articleMapper.selectById(articleId);
        if (null == article || null == article.getCounselorId()) {
            log.error("文章不存在或不符合规范：" + articleId);
            return null;
        }
        Counselor counselor = counselorMapper.selectById(article.getCounselorId());
        if (null == counselor) {
            log.error("该【" + articleId + "】文章的咨询师查询不到");
            return null;
        }
        ArticleDetailVO articleDetailVO = new ArticleDetailVO();
        articleDetailVO.setId(articleId);
        articleDetailVO.setTitle(article.getTitle());
        articleDetailVO.setAuthorAvatar(counselor.getAvatar());
        articleDetailVO.setAuthorName(counselor.getName());
        articleDetailVO.setPublishTimeStr(DateTimeUtil.getStandardDate(article.getPublishTime()));
        articleDetailVO.setArticleImg(article.getArticleImg());
        articleDetailVO.setText(article.getText());
        String key = buildReadArticleKey(articleId);
        if (redisUtil.hasKey(key)) {
            redisUtil.incr(key, 1);
            redisUtil.expire(key, ARTICLE_CACHE_EXPIRE_TIME);
        } else {
            redisUtil.set(key, article.getReadNum() + 1, ARTICLE_CACHE_EXPIRE_TIME);
        }
        return articleDetailVO;
    }

    @Override
    public Boolean isCollectArticle(CollectedParam param) {
        if (param == null || StrUtil.isEmpty(param.getUserId()) || StrUtil.isEmpty(param.getArticleId())) {
            return Boolean.FALSE;
        }
        String key = buildFollowArticleKey(param);
        Integer flag = redisUtil.get(key, Integer.class);
        if (null == flag) {
            Integer isSuccess = followRelationService.checkFollow(param);
            if (isSuccess > 0) {
                redisUtil.set(key, 1, ARTICLE_CACHE_EXPIRE_TIME);
                return Boolean.TRUE;
            } else {
                return Boolean.FALSE;
            }
        } else if (0 == flag) {
            return Boolean.FALSE;
        } else if (1 == flag) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    @Override
    public Boolean followArticle(CollectedParam param) {
        if (param == null || StrUtil.isEmpty(param.getUserId()) || StrUtil.isEmpty(param.getArticleId())) {
            return Boolean.FALSE;
        }
        try {
            String key1 = buildFollowArticleKey(param);                 // 用户收藏文章的缓存key
            boolean bool1 = redisUtil.set(key1, 1, ARTICLE_CACHE_EXPIRE_TIME);
            String key2 = buildFollowArticleKey(param.getArticleId());  // 文章被收藏数的缓存key
            boolean bool2;
            if (redisUtil.hasKey(key2)) {
                redisUtil.incr(key2, 1);
                bool2 = redisUtil.expire(key2, ARTICLE_CACHE_EXPIRE_TIME);
            } else {
                Article article = articleMapper.selectById(param.getArticleId());
                if (null == article) {
                    return Boolean.FALSE;
                }
                bool2 = redisUtil.set(key2, article.getCollectNum() + 1, ARTICLE_CACHE_EXPIRE_TIME);
            }
            return bool1 && bool2;
        } catch (Exception e) {
            log.error("收藏文章失败：" + e.getMessage());
            return Boolean.FALSE;
        }
    }

    @Override
    public Boolean unFollowArticle(CollectedParam param) {
        if (param == null || StrUtil.isEmpty(param.getUserId()) || StrUtil.isEmpty(param.getArticleId())) {
            return Boolean.FALSE;
        }
        try {
            String key1 = buildFollowArticleKey(param);                 // 用户收藏文章的缓存key
            boolean bool1 = false;
            String key2 = buildFollowArticleKey(param.getArticleId());  // 文章被收藏数的缓存key
            // redis 不包含此 key，为防止恶意请求，去数据库判断是否已收藏
            if (!redisUtil.hasKey(key1)) {
                Integer isSuccess = followRelationService.checkFollow(param);
                if (isSuccess > 0) {
                    bool1 = redisUtil.set(key1, 0, ARTICLE_CACHE_EXPIRE_TIME);
                }
            } else {
                bool1 = redisUtil.set(key1, 0, ARTICLE_CACHE_EXPIRE_TIME);
            }
            if (!bool1) {
                // bool1 为 false，说明要么恶意请求，要么数据库中不存在此条用户收藏关注数据，要么 redisUtil 命令失败
                return Boolean.FALSE;
            }
            if (redisUtil.hasKey(key2)) {
                redisUtil.decr(key2, 1);
                return redisUtil.expire(key2, ARTICLE_CACHE_EXPIRE_TIME);
            } else {
                Article article = articleMapper.selectById(param.getArticleId());
                if (null == article) {
                    return Boolean.FALSE;
                }
                return redisUtil.set(key2, article.getCollectNum() - 1, ARTICLE_CACHE_EXPIRE_TIME);
            }
        } catch (Exception e) {
            log.error("取消收藏文章失败：" + e.getMessage());
            return Boolean.FALSE;
        }
    }

    @Override
    public String saveArticle(ArticleDTO param) {
        LocalDateTime now = LocalDateTime.now();
        String counselorId = param.getCounselorId();
        Article article = new Article();
        article.setId(snowflake.nextStrId());
        article.setTitle(param.getTitle());
        article.setCounselorId(counselorId);
        article.setText(param.getText());
        article.setType(NavbarEnum.getCodeByName(param.getType()));
        article.setPublishTime(now);
        article.setCreateTime(now);
        article.setCreateUser(counselorId);
        article.setUpdateTime(now);
        article.setUpdateUser(counselorId);
        if (articleMapper.insert(article) > 0) {
            return article.getId();
        }
        return null;
    }

    @Override
    public Boolean modifyArticle(ArticleDTO param) {
        if (StrUtil.isBlank(param.getArticleId())) {
            return false;
        }
        return this.lambdaUpdate()
                .set(StrUtil.isNotBlank(param.getTitle()), Article::getTitle, param.getTitle())
                .set(StrUtil.isNotBlank(param.getText()), Article::getText, param.getText())
                .set(StrUtil.isNotBlank(param.getType()), Article::getType, param.getType())
                .set(Article::getUpdateTime, LocalDateTime.now())
                .eq(Article::getId, param.getArticleId())
                .update();
    }

    @Override
    public Boolean removeArticle(String articleId) {
        if (StrUtil.isBlank(articleId)) {
            return false;
        }
        return this.lambdaUpdate()
                .set(Article::getDeleteFlag, 1)
                .eq(Article::getId, articleId)
                .update();
    }

    private void buildStandardArticleVO(List<ArticleVO> articleVOList) {
        articleVOList.forEach(item -> {
            String title = item.getTitle();
            if (title.length() > 26) {
                item.setTitle(title.substring(0, 26) + "...");
            }
            item.setPublishTimeStr(DateTimeUtil.getStandardDate(item.getPublishTime()));
            Integer collectNum = redisUtil.get(buildFollowArticleKey(item.getId()), Integer.class);
            if (null != collectNum) {
                item.setCollectNum(collectNum);
            }
            Integer readNum = redisUtil.get(buildReadArticleKey(item.getId()), Integer.class);
            if (null != readNum) {
                item.setReadNum(readNum);
            }
        });
    }

}
